import os.path
import json
import math
import random
import re
import mplfinance as mpf
from jqdatasdk import *
from datetime import datetime
import numpy as np
import pandas as pd
import requests
import time
import shutil
from concurrent.futures import ThreadPoolExecutor


# auth(settings.usename, settings.password)
# auth("18281041357", "Cao13158696368")

######### 根据东方财富爬取数据的方法

def get_real_time_stock_price(code):
    """
    根据股票代码实时获取当时股价
    :param code:
    :return:
    """
    if str(code).startswith("3") or str(code).startswith("0"):
        secid = "0." + str(code)
    else:
        secid = "1." + str(code)
    params = {
        "cd": "jQuery3510892902004217601_" + str(time.time() * 1000 - 2).split(".")[0],
        "fields1": "f1,f2,f3,f4,f5",
        "fields2": "f51,f53,f59",
        "secid": secid,
        "ut": "fa5fd1943c7b386f172d6893dbfba10b",
        "lmt": "5",
        "klt": "101",
        "fqt": "1",
        "end": "20500000",
        "_": str(time.time() * 1000).split(".")[0]
    }
    url = "https://push2his.eastmoney.com/api/qt/stock/kline/get"
    hearder = {
        "cookie": 'qgqp_b_id=d52cecd898608d4ff6977afd6d23682b; HAList=ty-0-300059-%u4E1C%u65B9%u8D22%u5BCC%2Cty-0-300011-%u9F0E%u6C49%u6280%u672F%2Cty-0-300655-%u6676%u745E%u7535%u6750%2Cty-1-603977-%u56FD%u6CF0%u96C6%u56E2%2Cty-0-300243-%u745E%u4E30%u9AD8%u6750%2Cty-1-603367-%u8FB0%u6B23%u836F%u4E1A%2Cty-1-603196-%u65E5%u64AD%u65F6%u5C1A%2Cty-1-600744-%u534E%u94F6%u7535%u529B%2Cty-0-301129-%u745E%u7EB3%u667A%u80FD%2Cty-0-300778-%u65B0%u57CE%u5E02; st_si=68808354187413; st_asi=delete; st_pvi=62636193451663; st_sp=2023-04-18%2021%3A38%3A26; st_inirUrl=https%3A%2F%2Fcn.bing.com%2F; st_sn=5; st_psi=20230531215750160-113200301328-4807002491',
        "user-agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50'
    }
    res = requests.get(url=url, params=params, headers=hearder)
    try:
        data = res.json()
        rows = data['data']['klines']
        print(len(rows))
        for row in rows:
            print(row)
        today_date, today_price, today_add = str(data['data']['klines'][-1]).split(",")
        print(f"价格：{today_price} ，涨幅： {today_add} ")
    except Exception as e:
        print(e)
        print(f"【{code}】解析出错了---")


def get_interval_stork_data(code, limit=120):
    if str(code).startswith("3") or str(code).startswith("0"):
        secid = "0." + str(code)
    else:
        secid = "1." + str(code)
    params = {
        "cd": "jQuery3510892902004217601_" + str(time.time() * 1000 - 50).split(".")[0],
        "fields1": "f1,f2,f3,f4,f5,f6",
        "fields2": "f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61",
        "secid": secid,
        "ut": "fa5fd1943c7b386f172d6893dbfba10b",
        "lmt": str(limit),
        "klt": "101",
        "fqt": "1",
        "end": "20500101",
        "_": str(time.time() * 1000).split(".")[0]
    }

    url = "https://push2his.eastmoney.com/api/qt/stock/kline/get"
    hearder = {
        "cookie": 'qgqp_b_id=d52cecd898608d4ff6977afd6d23682b; HAList=ty-0-300059-%u4E1C%u65B9%u8D22%u5BCC%2Cty-0-300011-%u9F0E%u6C49%u6280%u672F%2Cty-0-300655-%u6676%u745E%u7535%u6750%2Cty-1-603977-%u56FD%u6CF0%u96C6%u56E2%2Cty-0-300243-%u745E%u4E30%u9AD8%u6750%2Cty-1-603367-%u8FB0%u6B23%u836F%u4E1A%2Cty-1-603196-%u65E5%u64AD%u65F6%u5C1A%2Cty-1-600744-%u534E%u94F6%u7535%u529B%2Cty-0-301129-%u745E%u7EB3%u667A%u80FD%2Cty-0-300778-%u65B0%u57CE%u5E02; st_si=68808354187413; st_asi=delete; st_pvi=62636193451663; st_sp=2023-04-18%2021%3A38%3A26; st_inirUrl=https%3A%2F%2Fcn.bing.com%2F; st_sn=5; st_psi=20230531215750160-113200301328-4807002491',
        "user-agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50'
    }
    res = requests.get(url=url, params=params, headers=hearder)
    data = res.json()
    if data['data'] is None:
        raise (f"解析该股票【{code}】 出错")
    try:
        rows = data['data']['klines']
        newRows = []
        # print(len(rows))
        for row in rows:
            newRows.append(str(row).split(","))
        df = pd.DataFrame(
            newRows,
            columns=["日期", "open", "close", "high", "low", "volume", "money", "振幅(%)", "涨幅(%)", "涨的金额", "换手率(%)"]
        )
        return df.set_index("日期")[["open", "close", "high", "low", "volume", "money", "涨幅(%)", "换手率(%)"]]
    except Exception as e:
        print(e)
        print(f"【{code}】解析出错了---")


def getMuchData(code, limit=300):
    """
    获取很多条数据
    :param code: 股票代码, str or int
    :param limit: 获取最新多少条数据， 默认是获取距离当天最近的300条数据
    :return:
    """
    if str(code).startswith("3") or str(code).startswith("0"):
        secid = "0." + str(code)
    else:
        secid = "1." + str(code)
    params = {
        "cd": "jQuery3510892902004217601_" + str(time.time() * 1000 - 50).split(".")[0],
        "fields1": "f1,f2,f3,f4,f5,f6",
        "fields2": "f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61",
        "secid": secid,
        "ut": "fa5fd1943c7b386f172d6893dbfba10b",
        "lmt": "120",
        "klt": "101",
        "fqt": "1",
        "end": "20500101",
        "beg": "0",
        "smplmt": str(limit),
        "_": str(time.time() * 1000).split(".")[0]
    }

    url = "https://push2his.eastmoney.com/api/qt/stock/kline/get"
    hearder = {
        "cookie": 'qgqp_b_id=d52cecd898608d4ff6977afd6d23682b; HAList=ty-0-300059-%u4E1C%u65B9%u8D22%u5BCC%2Cty-0-300011-%u9F0E%u6C49%u6280%u672F%2Cty-0-300655-%u6676%u745E%u7535%u6750%2Cty-1-603977-%u56FD%u6CF0%u96C6%u56E2%2Cty-0-300243-%u745E%u4E30%u9AD8%u6750%2Cty-1-603367-%u8FB0%u6B23%u836F%u4E1A%2Cty-1-603196-%u65E5%u64AD%u65F6%u5C1A%2Cty-1-600744-%u534E%u94F6%u7535%u529B%2Cty-0-301129-%u745E%u7EB3%u667A%u80FD%2Cty-0-300778-%u65B0%u57CE%u5E02; st_si=68808354187413; st_asi=delete; st_pvi=62636193451663; st_sp=2023-04-18%2021%3A38%3A26; st_inirUrl=https%3A%2F%2Fcn.bing.com%2F; st_sn=5; st_psi=20230531215750160-113200301328-4807002491',
        "user-agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50'
    }
    res = requests.get(url=url, params=params, headers=hearder)
    try:
        data = res.json()
        rows = data['data']['klines']
        print(len(rows))
        for row in rows:
            print(row)
        # today_date, today_price, today_add = str(data['data']['klines'][-1]).split(",")
        # print(f"价格：{today_price} ，涨幅： {today_add} ")
    except Exception as e:
        print(e)
        print(f"【{code}】解析出错了---")


def getNowTimes():
    timeStr = time.time() * 1000
    return str(timeStr).split(".")[0]


def randomInt16():
    r = "0123456789"
    num = ""
    for i in range(16):
        index = random.randint(0, 9)
        num += r[index]
    return num


def getAllStockInfo(market_cap=200):
    """
    f1: 2
    f2: 1.72  - 最新价格
    f3: 10.26 - 当前涨跌幅（%）
    f4: 0.16  - 涨跌额
    f5: 5518707  - 成交量
    f6: 926835688.98 - 成交额
    f7: 7.69  - 振幅（%）
    f8: 14.16 - 换手率（%）
    f9: -2.91 - 市盈率（动态）
    f10: 5.05
    f11: 0
    f12: "002146" - 股票代码
    f13: 0
    f14: "荣盛发展" - 股票名称
    f15: 1.72  - 最高价
    f16: 1.6  - 最低价
    f17: 1.72 - 开盘价
    f18: 1.56  - 昨日收盘价
    f20: 7478841824  - 总市值
    f21: 6702016724  - 流通市值
    f22: 0
    f23: 0.33  - 市净率
    f24: -16.1  - 60日涨跌幅（%）
    f25: -20.74 - 年初至今涨跌幅
    f62: -10359694
    f115: -0.47
    f128: "-"
    f136: "-"
    f140: "-"
    f141: "-"
    f152: 2
    :return:
    """
    url = "http://20.push2.eastmoney.com/api/qt/clist/get"
    params = {
        "cb": "jQuery11240" + randomInt16() + "_" + getNowTimes(),
        "pn": "1",  # 页码
        "pz": "6000",  # 每页多少数据
        "po": "1",
        "np": "1",
        "ut": "bd1d9ddb04089700cf9c27f6f7426281",  # 固定参数
        "fltt": "2",
        "invt": "2",
        "wbp2u": "|0|0|0|web",
        "fid": "f5",
        "fs": "m:0 t:6,m:0 t:80,m:1 t:2,m:1 t:23,m:0 t:81 s:2048",
        "fields": 'f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f12,f13,f14,f15,f16,f17,f18,f20,f21,f23,f24,f25,f22,f11,f62,f128,f136,f115,f152',
        "_": str(int(getNowTimes()) + 1),
    }
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",
        "Cookie": 'qgqp_b_id=c8474bc3080744818505c8fca9c4b4bc; st_si=07372394619606; EMFUND1=null; EMFUND2=null; EMFUND3=null; EMFUND4=null; EMFUND5=null; EMFUND6=null; EMFUND7=null; EMFUND8=null; EMFUND0=null; EMFUND9=06-14 14:31:36@#$%u91D1%u9E70%u6DFB%u76C8%u7EAF%u503A%u503A%u5238A@%23%24003384; HAList=ty-0-300059-%u4E1C%u65B9%u8D22%u5BCC%2Cty-0-300248-%u65B0%u5F00%u666E; emshistory=%5B%22%E5%B8%82%E5%80%BC%E6%8E%92%E8%A1%8C%22%5D; st_asi=delete; st_pvi=96838796122148; st_sp=2023-06-14%2014%3A06%3A49; st_inirUrl=https%3A%2F%2Fwww.baidu.com%2Flink; st_sn=94; st_psi=20230614151702337-113200301321-7033476658',
        'Host': '40.push2.eastmoney.com',
        'Referer': 'http://quote.eastmoney.com/center/gridlist.html?st=Volume&sr=-1'
    }

    res = requests.get(url=url, params=params, headers=headers)
    res = res.content.decode("utf-8")
    text = re.findall(r'\(.*\)', res)

    data_dict = json.loads(text[0][1:-1])
    filterList = []
    for row in data_dict['data']['diff']:
        row_data = {"code": "", "display_name": "", "market_cap": ""}
        stockCode = row['f12']
        stockName = row['f14']
        stockMarket = row['f20']
        if str(stockName).startswith("退市") or str(stockName).startswith("ST") or str(stockName).startswith("*ST"):
            continue
        if stockMarket == "-":
            continue
        if str(stockCode).startswith("688") or str(stockCode).startswith("8") or str(stockCode).startswith("4"):
            continue
        if 10 * 100000000 < int(stockMarket) < market_cap * 100000000:
            row_data['code'] = stockCode
            row_data['display_name'] = stockName
            row_data['market_cap'] = stockMarket
            filterList.append(row_data)
    df = pd.DataFrame(filterList)
    return df.set_index("code")


######


def get_count():
    """获取接口每天剩余的查询次数"""
    print(get_query_count())


"""
# 常用接口
 -1. 获取指定范围的所有交易日，
    start_date: 开始日期, 与 count 二选一, 不可同时使用. str/[datetime.date]/[datetime.datetime] 对象
    end_date: 结束日期, str/[datetime.date]/[datetime.datetime] 对象, 默认为 datetime.date.today()
    count: 数量, 与 start_date 二选一, 不可同时使用, 必须大于 0. 表示取 end_date 往前的 count 个交易日，包含 end_date 当天。
    get_trade_days(start_date=None, end_date=None, count=None)
 
 -2.获取所有交易日
  不需要传入参数, 返回一个包含所有交易日的 [numpy.ndarray], 每个元素为一个 [datetime.date] 类型.
  get_all_trade_days()
  
 - 3. 判断是否ST/基金净值/期货结算价
    get_extras(info, security_list, start_date='2015-01-01', end_date='2015-12-31', df=True, count=None)
    info: ['is_st', 'acc_net_value', 'unit_net_value', 'futures_sett_price', 'adj_net_value'] 中的一个
    
    指定info字段	返回信息
    is_st	是否是ST，是则返回 True，否则返回 False
    acc_net_value	基金累计净值
    unit_net_value	基金单位净值
    futures_sett_price	期货结算价
    adj_net_value	场外基金的复权净值
    security_list: 标的列表
    
    start_date/end_date: 开始结束日期, 同 [get_price]
    
    df: 返回[pandas.DataFrame]对象或者dict
    
    count: 数量, 与 start_date 二选一, 不可同时使用, 必须大于 0. 表示取 end_date 往前的 count 个交易日的数据
    
    返回值
    
    df=True: 返回[pandas.DataFrame]对象, 列索引是股票代号, 行索引是[datetime.datetime]
    df=False，返回一个dict, key是股票代号，如
    以股票为例
    
    # 平安银行和神城A在2013-12-01至2013-12-03是否为ST
    get_extras('is_st', ['000001.XSHE', '000018.XSHE'], start_date='2013-12-01', end_date='2013-12-03')
    
    # 输出：
                000001.XSHE  000018.XSHE
    2013-12-02        False         True
    2013-12-03        False         True
    
"""


def get_stock_info(stock_code):
    """
    获取单只股票的信息
    :param stock_code: 股票代码
    :return:一个对象, 有如下属性:
        display_name # 中文名称
        name # 缩写简称
        start_date # 上市日期, [datetime.date] 类型
        end_date # 退市日期， [datetime.date] 类型, 如果没有退市则为2200-01-01
        type # 类型，stock(股票)，index(指数)，etf(ETF基金)，fja（分级A），fjb（分级B）
        parent # 分级基金的母基金代码
    """
    return get_security_info(normalize_code(stock_code))


def get_Oneday_all_securities(type=[], date=None):
    """
    获取某一天所有还在上市的股票、基金、指数、期货信息
    默认是获取当天所有还在上市的股票
    :param type: 需要获取的数据类型，为空时默认只获取的时股票数据
        {
            stock：股票，
            index：指数，
            etf：ETF基金，
            fja：分级A，
            fjb：分级B，
            fjm：分级母基金，
            mmf：场内交易的货币基金，
            open_fund：开放式基金，
            bond_fund：债券基金，
            stock_fund: 股票型基金,
            QDII_fund: QDII 基金,
            money_market_fund: 场外交易的货币基金,
            mixture_fund: 混合型基金,
            options: 期权
        }
    :param date: 想获取哪一天的数据，默认为当天时间
    :return:
    """
    if not date:
        date = datetime.now().strftime("%Y-%m-%d")
    return get_all_securities(types=type, date=date)


def get_share_close_price(stock_code, start_date, end_date, count=None,
                          fields=('circulating_cap', 'circulating_market_cap')):
    """
    获取一个或多个股票范围时间内的每天收盘价
    :param stock_code: 股票代码， e.g: "603392"
    :param start_date: 开始时间， e.g: "2022-01-01"
    :param end_date: 结束时间， e.g: "2022-12-31"
    :param count: 范围时间内，截取的数据条数， count和start_date必须至少传一个
    :param fields: 需要获取哪些列表的数据，以下是所有列名，可以选择其中某些列
        {
            code：股票代码，
            day ：日期， 取数据的日期，
            capitalization：总股本（万股），
            circulating_cap： 流通股本（万股），
            market_cap： 总市值（亿元），
            circulating_market_cap: 流通市值（亿元），
            turnover_ratio ：换手率（%），
            pe_ratio ：市盈率(PE, TTM)，
            pe_ratio_lyr ：市盈率(PE)，
            pb_ratio： 市净率(PB)，
            ps_ratio： 市销率(PS, TTM)，
            pcf_ratio： 市现率(PCF, 现金净流量TTM)
        }
    :return:
    """
    if not start_date and not count:
        raise KeyError("start_date 和 count 必须有一个存在！！！")
    security = normalize_code(stock_code)
    df = get_valuation(security, start_date=start_date, end_date=end_date, count=count, fields=list(fields))
    df_new = df.assign(price=lambda x: x['circulating_market_cap'] / (x["circulating_cap"] / 10000))
    print(df_new)
    return df_new


# get_share_price("603392",start_date="2023-01-01", end_date="2023-04-14")

def get_stock_all_price(stock_code, start_date=None, end_date=None, frequency='daily', fields=None, skip_paused=True,
                        fq='pre', count=None):
    """
    获取一支或者多只股票某个时间范围内的每天的所有价格（开盘价， 最高价， 最低价，收盘价， 成交量）
    :param stock_code: 股票代码
    :param start_date: 开始时间
    :param end_date: 结束时间
    :param frequency: 单位时间长度，即指定获取的时间频级为分钟级（minute）或日级（daily）,也可以指定为 '3m','10d' 等
    :param fields:所获取数据的字段名称，即表头。默认是None(返回标准字段['open','close','high','low','volume','money'])
        ['open','close','low','high','volume','money','factor',
        'high_limit','low_limit','avg','pre_close','paused','open_interest'],
        open_interest为期货持仓量
    :param skip_paused:是否跳过不交易日期(含：停牌/未上市/退市后的日期)，如果不跳过, 停牌时会使用停牌前的数据填充，上市前或者退市后数据都为 nan。
    :param fq:复权选项，默认为前复权（fq='pre'）
    :param count:范围时间内，截取的数据条数， count和start_date必须至少传一个，不能同时传

    open	时间段开始时价格
    close	时间段结束时价格
    low	时间段中的最低价
    high	时间段中的最高价
    volume	时间段中的成交的股票数量
    money	时间段中的成交的金额
    factor	pre':前复权(默认)/None:不复权,返回实际价格/'post':后复权
    前(后)复权数据=价格×前(后)复权因子;

    前(后)复权后的成交量=成交量 / 前(后)复权因子;

    成交不额处理

    high_limit	时间段中的涨停价
    low_limit	时间段中的跌停价
    avg	时间段中的平均价
    (1)天级别：股票是成交额除以成交量；期货是直接从CTP行情获取的，计算方法为成交额除以成交量再除以合约乘数；

    （2）分钟级别：用该分钟所有tick的现价乘以该tick的成交量加起来之后，再除以该分钟的成交量。

    pre_close	前一个单位时间结束时的价格,按天则是前一天的收盘价	期货：pre_close--前一天结算；建议使用get_extras获取结算价；在分钟频率下pre_close=open
    paused	bool值,股票是否停牌;	停牌时open/close/low/high/pre_close；都等于停牌前的收盘价, volume=money=0
    open_interest	期货(期权)持仓量

    :return:
    """
    security = normalize_code(stock_code)
    return get_price(security, start_date=start_date, end_date=end_date, frequency=frequency, fields=fields,
                     skip_paused=skip_paused, fq=fq, count=count)


"""
# 策略定制
 - 1.以当天时间为标准，前两个月的价格比较平稳，且最近两天每天价格涨幅大于 3%~5%,且价格高于最近的一个金叉点的股价
    - 平稳的依据是前两个月每天价格波动在 -4%~4%，一直横盘震荡2个月
    - 金叉的时机是指 5日均线穿过10日均线
 - 2.小市值股票，市值小于200亿
"""


def get_range_market_cap(max_market_cap):
    """
    获取市值小于 max_market_cap 的所有股票代码, 默认顺序为市值降序
    :param max_market_cap: 最大市值(亿元)，int
    :return:
    """
    if not isinstance(max_market_cap, int):
        raise ValueError("max_market_cap 必须为整数类型")
    df = get_fundamentals(query(
        valuation.code, valuation.market_cap, valuation.pe_ratio, income.basic_eps, income.total_operating_revenue
    ).filter(
        valuation.market_cap < int(max_market_cap)
        # valuation.pe_ratio < 10,
        #  income.basic_eps  > 0.8
    ).order_by(
        # 按市值降序排列
        valuation.market_cap.desc()
    ))
    # return [stock_code[0] for stock_code in df[["code"]].values]
    return df


def get_stock_zf(stock_code, day_date):
    """
    获取某只股票某天的涨幅或者跌幅, 如果查询的日期不是交易日，则获取最近的一个交易日的涨幅
    :param stock_code: 股票代码
    :param day_date: 某天的日期
    :return:
    """
    trade_day = [i for i in get_trade_days(end_date=day_date, count=2)]
    yesterday, today = trade_day[0], trade_day[1]
    df = get_stock_all_price(stock_code, start_date=yesterday, end_date=today)
    yesterday_price = list(df[['close']].values)[0][0]
    today_price = list(df[['close']].values)[1][0]
    zf = (today_price - yesterday_price) / yesterday_price
    return round(zf, 4)


def get_average_line(stock_code_path, lines=[5, 20]):
    """
    获取任意时间范围内的均线,默认返回5日均线
    :param stock_code: 股票代码
    :param line: 均线选择，list (5:代表5日均线， 10：代表10日均线 ....)
    :param start_date: 开始日期， 和count二选一
    :param end_date: 结束日期
    :param count: 以结束时间为基准，向前推多个交易日的日期范围， 和start_date二选一
    :return:
    """
    df = pd.read_csv(f"allData/{stock_code_path}")
    # 方法1: 用rolling方法生成均线
    for line in lines:
        df[f"md{str(line)}"] = np.nan
        df[f"md{str(line)}"] = df['close'].rolling(line).mean()

    # 方法2: 用循环获取均线
    # for i in range(4,len(df)):
    #     df.loc[df.index[i], "md5"] = df["close"][i-4:i+1].mean()

    # df.dropna()  # 去掉缺失值NaN
    if not os.path.exists("avgLineData"):
        os.mkdir("avgLineData")
    df.to_csv(f"avgLineData/{stock_code_path}")


def get_range_date_jc(stock_code, lines=[5, 20]):
    """
    获取某个股票的两个均线的金叉和死叉
    :param stock_code: 股票代码
    :param lines: 均线范围（5： 代表5日均线， 10：代表10日均线）
    :param start_date: 开始日期， 和count二选一
    :param end_date: 结束日期
    :param count: 以结束时间为基准，向前推多个交易日的日期范围， 和start_date二选一
    :return:
    """
    min_line = f"md{str(lines[0])}"
    max_line = f"md{str(lines[1])}"
    df = get_average_line(stock_code, lines=[5, 10])
    sr1 = df[min_line] < df[max_line]
    sr2 = df[min_line] >= df[max_line]
    death_list = df[sr1 & sr2.shift(1)].index
    golden_list = df[~(sr1 | sr2.shift(1))].index
    df["jc"] = np.nan
    for i in range(0, len(df)):
        if df.index[i] in golden_list:
            df.loc[df.index[i], "jc"] = "金叉"
        if df.index[i] in death_list:
            df.loc[df.index[i], "jc"] = "死叉"
    return df


def is_in_range_price(stock_code, price_range=[-0.04, 0.04], start_date=None, end_date=None, count=None):
    """
    判断某只股票在指定时间范围内，价格的涨跌幅是否一直在一个区间
    :param stock_code: 股票代码
    :param price_range: 涨跌幅范围
    :param start_date: 开始日期， 和count二选一
    :param end_date: 结束日期
    :param count: 以结束时间为基准，向前推多个交易日的日期范围， 和start_date二选一
    :return:
    """
    df = get_stock_all_price(stock_code, start_date=start_date, end_date=end_date, count=count)
    init_price = df["close"][0]
    min_price = init_price + (init_price * price_range[0])
    max_price = init_price + (init_price * price_range[1])
    for i in range(0, len(df)):
        if min_price <= df['close'][i] <= max_price:
            continue
        else:
            return False


def batch_get_all_stock_info(limit=62):
    """
    读取小市值公司的csv文件，保存所有小市值公司最近 `limit` 天的数据
    主要是每天的价格数据
    :param limit: 最近多少天的数据，默认是62天
    :return:
    """
    print(f"开始保存每个股票的{limit}天的数据....")
    # 读取包含所有小市值公司的csv文件，并以日期为索引
    all_stork_df = pd.read_csv("allStockData/small_info.csv", index_col=["code"])
    all_stork_code = all_stork_df.index
    pool = ThreadPoolExecutor(20)
    if not os.path.exists("allData"):
        os.mkdir("allData")

    def _task(code, li):
        try:
            current_code_pd = get_interval_stork_data(code, li)

            save_path = os.path.join("allData", code + ".csv")
            current_code_pd.to_csv(save_path)
        except Exception as e:
            print(e)
            print(f"【{code}】爬取数据可能出错~~~~~")

    for i in all_stork_code:
        storc_code = (str(i).split(".")[0]).zfill(6)
        pool.submit(_task, storc_code, limit)

    pool.shutdown(True)
    print("每个股票的数据已经保存完毕!!!!")


def create_md_avg_line():
    """
    根据每个股票的价格数据，去生成 5日均线， 10日均线， 20日均线
    :return:
    """
    print("开始生成每个股票的均线数据....")
    file_paths = os.listdir("allData")
    linePool = ThreadPoolExecutor(20)
    for path in file_paths:
        linePool.submit(get_average_line, path, [5, 10, 20])
    linePool.shutdown(True)
    print("均线数据生成完毕!!!")


def batch_get_jc(lines=[5, 20]):
    """
    根据均线去生成金叉和死叉， 默认是生成 5日均线和20日均线的金叉和死叉
    :param lines: 传入一个列表，必须是两个均线的数字， 且数据中已经存在的均线
    :return:
    """
    min_line = f"md{str(lines[0])}"
    max_line = f"md{str(lines[1])}"
    print(f"开始生成{min_line}日均线和{max_line}日均线的金叉和死叉...")
    file_paths = os.listdir("avgLineData")

    jcPool = ThreadPoolExecutor(20)

    def _task(csvPath):
        read_file_path = os.path.join("avgLineData", csvPath)
        df = pd.read_csv(read_file_path, index_col=["Unnamed: 0"]).dropna()
        sr1 = df[min_line] < df[max_line]
        sr2 = df[min_line] >= df[max_line]
        death_list = df[sr1 & sr2.shift(1)].index
        golden_list = df[~(sr1 | sr2.shift(1))].index
        jc_name = f"jc_{str(lines[0])}_{str(lines[1])}"
        df[jc_name] = np.nan
        for i in range(0, len(df)):
            if df.index[i] in golden_list:
                df.loc[df.index[i], jc_name] = "金叉"
            if df.index[i] in death_list:
                df.loc[df.index[i], jc_name] = "死叉"
        jc_path = f"allJcData_{str(lines[0])}_{str(lines[1])}"
        if not os.path.exists(jc_path):
            os.mkdir(jc_path)
        df.to_csv(f"{jc_path}/{csvPath}")

    for path in file_paths:
        jcPool.submit(_task, path)
    jcPool.shutdown(True)
    print(f"{min_line}日均线和{max_line}日均线的金叉和死叉生成完毕！！！")


def get_K_line_panel(stock_code, save_path, title, mav=(5, 10)):
    """
    根据每个股票最近2个月的数据，画出K线图
    :param stock_code:
    :param save_path:
    :param title:
    :param mav:
    :return:
    """
    df = pd.read_csv(f"avgLineData/{stock_code}")
    df["Date"] = pd.to_datetime(df["日期"])
    df.index = df["Date"]
    new_df = df[["Date", "open", "high", "low", "close", "volume"]]
    mpf.plot(
        new_df,
        type="candle",
        ylabel="price",
        style="blueskies",
        title=title,
        mav=mav,
        volume=True,
        figratio=(5, 3),
        ylabel_lower="Volume",
        savefig=save_path
    )


def batch_get_meet_conditions():
    """
    获取满足条件的所有股票，主要是去获取均线csv文件最近5天存在金叉的所有股票
    :param end_date:
    :return:
    """
    print("开始根据金叉画K线图....")
    if not os.path.exists("imgs"):
        os.mkdir("imgs")
    if not os.path.exists("result"):
        os.mkdir("result")

    def _get_increase(lst):
        if len(lst) < 5:
            return None
        for increase in lst:
            if increase > 9:
                return None
        return lst

    avgline_5_10_files = os.listdir("allJcData_5_10")
    avgline_5_20_files = os.listdir("allJcData_5_20")
    if not os.path.exists("imgs/img_5_10"):
        os.mkdir("imgs/img_5_10")
    if not os.path.exists("imgs/img_5_20"):
        os.mkdir("imgs/img_5_20")
    meet_conditions_5_10_list = []
    meet_conditions_5_20_list = []

    for path in avgline_5_10_files:
        stock_code = str(path).split(".")[0]
        df = pd.read_csv(f"allJcData_5_10/{path}")[-5:]
        price_zf_list = _get_increase(df['涨幅(%)'].tolist())
        if price_zf_list is None:
            continue
        try:
            # 金叉后的第1个涨幅
            first_zf = price_zf_list[-2]
            # 金叉后的第二个涨幅
            last_zf = price_zf_list[-1]
            meet_conditions_sc = df[df["jc_5_10"] == "死叉"]
            if (df['jc_5_10'].iloc[2] == "金叉") and len(meet_conditions_sc) <= 0:
                if (0 < float(first_zf) < 6) and (2 <= float(last_zf) <= 6):
                    get_K_line_panel(path, f"imgs/img_5_10/{stock_code}", f"{stock_code}-{first_zf} + {last_zf}")
                    meet_conditions_5_10_list.append(stock_code)
                    print(f"{stock_code} K线图完成")
        except Exception as e:
            pass

    for path in avgline_5_20_files:
        stock_code = str(path).split(".")[0]
        df = pd.read_csv(f"allJcData_5_20/{path}")[-5:]
        price_zf_list = _get_increase(df['涨幅(%)'].tolist())
        if price_zf_list is None:
            continue
        try:
            # 金叉后的第1个涨幅
            first_zf = price_zf_list[-2]
            # 金叉后的第二个涨幅
            last_zf = price_zf_list[-1]
            meet_conditions_sc = df[df["jc_5_20"] == "死叉"]
            if (df['jc_5_20'].iloc[2] == "金叉") and len(meet_conditions_sc) <= 0:
                if (0 < float(first_zf) < 6) and (2 <= float(last_zf) <= 6):
                    get_K_line_panel(path, f"imgs/img_5_20/{stock_code}", f"{stock_code}-{first_zf} + {last_zf}")
                    meet_conditions_5_20_list.append(stock_code)
                    print(f"{stock_code} K线图完成")
        except Exception as e:
            pass

    if len(meet_conditions_5_10_list) > 0 or len(meet_conditions_5_20_list) > 0:
        df1 = pd.DataFrame({"stock_code": meet_conditions_5_10_list})
        df2 = pd.DataFrame({"stock_code": meet_conditions_5_20_list})

        df1.to_csv("result/meet_conditions_5_10.csv", index=False)
        df2.to_csv("result/meet_conditions_5_20.csv", index=False)
        print("K线图已绘制完成！！！！")
    else:
        print("K线图没有过滤出符合条件的股票")


def save_all_small_company_info(all_stock_path, small_200_stock_path):
    """
    根据所有股票信息和小于200亿的股票信息，进行筛选
    :param all_stock_path: 包含所有股票信息的csv文件路径
    :param small_200_stock_path:  包含所有小于200亿股票信息的csv文件路径
    :return:
    """
    # 读取所有股票的csv文件
    all_stork_df = pd.read_csv(all_stock_path, index_col=["Unnamed: 0"])
    # 读取所有市值小于200亿的公司的csv文件
    all_200E_stork_df = pd.read_csv(small_200_stock_path, index_col=["code"])

    all_stork_df["market_cap"] = np.nan
    # 将 df文件转换成dict数据
    new_df = json.loads(all_200E_stork_df["market_cap"].to_json())

    for i in range(0, len(all_stork_df)):
        # 如果是ST股票就去掉
        if "ST" in all_stork_df["display_name"][i]:
            continue
        # 过滤出市值小于200亿，且股票代码不是688开头的（因为现在我账号买不了688开头的股票）
        if all_stork_df.index[i] in new_df and not all_stork_df.index[i].startswith("688"):
            all_stork_df.loc[all_stork_df.index[i], "market_cap"] = new_df.get(all_stork_df.index[i])
    # 把存在NaN的数据删除掉，且将数据按照市值降序排列
    filter_df = all_stork_df.dropna().sort_values("market_cap", ascending=False)
    filter_df.to_csv("allStockData/small_info.csv")


def get_final_stock_info():
    print("开始生成Excel表格....")
    try:
        result_5_10_path = "result/meet_conditions_5_10.csv"
        df1 = pd.read_csv(result_5_10_path)
    except FileNotFoundError as e:
        print(e)
        pass
    try:
        result_5_20_path = "result/meet_conditions_5_20.csv"
        df2 = pd.read_csv(result_5_20_path)
    except FileNotFoundError as e:
        print(e)
        print("没有过滤出符合条件的股票")
        return
    final_list = []

    # def _get_stock_name_and_market_cap(stock_code):
    #     """获取股票名称和市值大小"""
    #     small_info_path = "allStockData/small_info.csv"
    #     df = pd.read_csv(small_info_path)
    #     df["code"] = df['code'].apply(lambda x: str(x).zfill(6))
    #     res = df.loc[df['code'] == str(stock_code).zfill(6)]
    #     res = res.iloc[0].tolist()
    #     stock_name = res[1]
    #     market_cap = res[2]
    #     return stock_name, market_cap, str(stock_code).zfill(6)
    #
    # def _get_now_zf(stock_code):
    #     """获取当日涨幅"""
    #     stoc_name, market_cap, index = _get_stock_name_and_market_cap(stock_code)
    #     stock_info_path = f"allData/{index}.csv"
    #     df = pd.read_csv(stock_info_path)
    #     first_price = df["close"].iloc[-2]
    #     last_price = df["close"].iloc[-1]
    #     price_zf = str(math.ceil(((last_price - first_price) / first_price) * 10000) / 100) + "%"
    #     return stoc_name, market_cap, index, price_zf, last_price

    def _get_now_info(stock_code):
        small_info_path = "allStockData/small_info.csv"
        df = pd.read_csv(small_info_path)
        df["code"] = df['code'].apply(lambda x: str(x).zfill(6))
        res = df.loc[df['code'] == str(stock_code).zfill(6)]
        res = res.iloc[0].tolist()
        stock_name = res[1]
        market_cap = int(res[2]) / 100000000
        index = str(stock_code).zfill(6)
        stock_info_path = f"allData/{index}.csv"
        stock_df = pd.read_csv(stock_info_path)
        price_zf = stock_df['涨幅(%)'].iloc[-1]
        last_price = stock_df["close"].iloc[-1]
        turnover_rate = stock_df["换手率(%)"].iloc[-1]
        return stock_name, market_cap, index, price_zf, turnover_rate, last_price

    for stock_code in df1["stock_code"].tolist():
        row_data = []
        stoc_name, market_cap, index, price_zf, turnover_rate, last_price = _get_now_info(stock_code)
        row_data.append("5-10均线")
        row_data.append(index)
        row_data.append(stoc_name)
        row_data.append(market_cap)
        row_data.append(last_price)
        row_data.append(turnover_rate)
        row_data.append(price_zf)
        row_data.append("0")
        final_list.append(row_data)

    final_list.append(["总涨幅", "", "", "", "", "", "", ""])
    final_list.append(["", "", "", "", "", "", "", ""])
    for stock_code in df2["stock_code"].tolist():
        row_data = []
        stoc_name, market_cap, index, price_zf, turnover_rate, last_price = _get_now_info(stock_code)
        row_data.append("5-20均线")
        row_data.append(index)
        row_data.append(stoc_name)
        row_data.append(market_cap)
        row_data.append(last_price)
        row_data.append(turnover_rate)
        row_data.append(price_zf)
        row_data.append("0")
        final_list.append(row_data)
    final_list.append(["总涨幅", "", "", "", "", "", "", ""])
    final_df = pd.DataFrame(final_list, columns=["均线类别", "股票代码", "股票名称", "市值大小(亿)", "当日价格", "换手率(%)", "当日涨幅(%)", "次日涨幅"])
    nowDate = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    final_df.to_excel(f"result/result_excel_{nowDate}.xlsx", index=False)
    print("Excel表格已生成！！！")


def deteleDir(path):
    try:
        shutil.rmtree(path)
    except FileNotFoundError as e:
        pass

def deteleAllFile():
    """删除之前的生成的数据"""
    deteleDir("allData")
    deteleDir("allStockData")
    deteleDir("allJcData_5_10")
    deteleDir("allJcData_5_20")
    # deteleDir("result")
    deteleDir("imgs")
    deteleDir("avgLineData")
    print("已删除之前的数据...")

def run(market_cap=200):
    """
    默认是过滤市值小于200亿的公司
    :param market_cap: 最大市值
    :return:
    """
    # deteleAllFile()
    # # 1. 获取当前时间还在上市的所有股票信息,并保存
    # if not os.path.exists("allStockData"):
    #     os.mkdir("allStockData")
    # all_stock = get_Oneday_all_securities()
    # all_stock.to_csv("allStockData/所有股票.csv")
    #
    # # 2. 获取所有市值小于200E的股票
    # all_200E_stock = get_range_market_cap(market_cap)
    # all_200E_stock.to_csv("allStockData/200E.csv")
    # # 根据以上的数据，进行过滤，筛选出小于200E的股票，且删掉ST和688开头的股票
    # save_all_small_company_info("allStockData/所有股票.csv", "allStockData/200E.csv")

    deteleAllFile()
    if not os.path.exists("allStockData"):
        os.mkdir("allStockData")
    filter_df = getAllStockInfo(market_cap)
    filter_df.to_csv("allStockData/small_info.csv")
    print("small_info.csv 文件已生成...")
    # 保存每个小市值股票的价格信息
    batch_get_all_stock_info()

    # 批量去生成每个股票的 5日均线，10日均线，20日均线
    create_md_avg_line()

    # 生成 5日均线和20日均线的金叉和死叉
    batch_get_jc(lines=[5, 20])

    # 生成 5日均线和10日均线的金叉和死叉
    batch_get_jc(lines=[5, 10])


if __name__ == '__main__':

    start = time.time()
    run(1000)

    # 获取最近5天存在金叉的所有股票
    batch_get_meet_conditions()

    # 生成最后符合条件的excel数据
    get_final_stock_info()

    # 查询一下当天接口剩余次数
    # get_count()
    # get_final_stock_info()
    # get_K_line_panel("000006.XSHE.csv", "000006.XSHE.png")
    print("总耗时（分钟）:", (time.time() - start) / 60)
